/*
 *  Copyright (C) 2010 Glauco
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.balisunrise.annotation.util;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Properties;
import org.balisunrise.annotation.ColumnDefinition;
import org.balisunrise.annotation.ColumnEmbedded;
import org.balisunrise.annotation.ColumnSelection;
import org.balisunrise.annotation.GridColumn;
import org.balisunrise.annotation.Selection;
import org.balisunrise.application.Application;
import org.balisunrise.common.PropertiesReader;

/**
 *
 * @author glacuo
 */
public class ColumnUtilities {

    private static final String MESSAGE = "Erro ao ler Annotation \"GridColumn\".";
    public static final int DEFINITION = 1;
    public static final int EMBEDDED = 2;
    public static final int SELECTION = 3;

    /**
     * Atributo com a anotação <code>@GridColumn</code> da classe de uma entidade.
     */
    public Field field;

    /**
     * Alias (nome) da definição.
     */
    public String alias;

    /**
     * Indice em que o atributo deve aparecer na tela.
     */
    public int index;

    /**
     * Nome da coluna no grid.
     */
    public String name;

    /**
     * Indica se a coluna do grid será editável.
     * Esta opção não é utilizada em buscas ou pesquisa, onde todas as colunas
     * serão apenas para mostrar os dados, e não altera-los.
     */
    public boolean editable;

    /**
     * Largura da Coluna em pixel.
     */
    public int width;

    /**
     * Alias do atributo que será usada para construir os campo de uma coluna
     * de uma entidade associada encapsulada.
     */
    public String call;

    /**
     * alias da tela de pesquisa do atributo de seleção.
     */
    public String search;

    /**
     * Mensagem para quando uma entidade de seleção não foi encontrada.
     */
    public String notFoundMessage;

    /**
     * Tipo da anotação de <code>@PanelField</code>.
     * @see ColumnUtilities#DEFINITION
     * @see ColumnUtilities#EMBEDDED
     * @see ColumnUtilities#SELECTION
     */
    public int type;

    /**
     * Propriedades do campo da entidade.
     */
    public Properties properties;

    /**
     * Determina se este campo é utilizado para seleção de entidades.
     */
    public boolean selection;

    /**
     * Campos encapsulados ou de seleção.
     */
    public List<ColumnUtilities> embedded;

    /**
     *
     * @param field
     * @param alias
     * @return
     * @throws AnnotationsUtilitiesException
     */
    public static ColumnUtilities create(Field field, String alias)
            throws AnnotationsUtilitiesException{

        if (field == null)
            throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nParâmentro \"field\" não pode ser nulo!");

        if(alias == null)
            throw new AnnotationsUtilitiesException(MESSAGE +
                    "\nParâmentro \"alias\" não pode ser nulo!");

        GridColumn gc = readGridColumnAnnotation(field);

        ColumnUtilities gcu = new ColumnUtilities();
        gcu.field = field;
        gcu.alias = alias;
        gcu.selection = isSelection(field, alias);

        PropertiesReader pr = Application.get().getPropertiesReader();
        Properties p = new Properties();
        if (pr != null)
                p.putAll(pr.readFieldProperties(field));

        try{
            ColumnDefinition cd = readColumnDefinition(gc, alias);
            gcu.name = cd.name();
            gcu.width = cd.width();
            gcu.editable = cd.editable();
            gcu.index = cd.index();
            gcu.type = DEFINITION;
            if (pr != null)
                p.putAll(pr.readColumnDefinitionProperties(cd));
            gcu.properties = p;
            return gcu;
        }catch(AnnotationsUtilitiesException aue){
            // não trata nada aqui pois a exceção é disparada no final
        }

        try{
            ColumnEmbedded ce = readEmbeddedColumns(gc, alias);
            gcu.index = ce.index();
            gcu.call = ce.grid();
            gcu.type = EMBEDDED;
            if (pr != null)
                p.putAll(pr.readColumnEmbeddedProperties(ce));
            gcu.properties = p;
            return gcu;
        }catch(AnnotationsUtilitiesException aue){
            // não trata nada aqui pois a exceção é disparada no final
        }

        try{
            ColumnSelection cs = readSelectionColumns(gc, alias);
            gcu.index = cs.index();
            gcu.call = cs.grid();
            gcu.search = cs.search();
            gcu.notFoundMessage = cs.notFoundMessage();
            gcu.type = SELECTION;
            if (pr != null)
                p.putAll(pr.readColumnSelectionProperties(cs));
            gcu.properties = p;
            return gcu;
        }catch(AnnotationsUtilitiesException aue){
            // não trata nada aqui pois a exceção é disparada no final
        }

        throw new AnnotationsUtilitiesException(
                "Erro ao criar \"ColumnDefinition\"!" +
                "\nAnnotation \"ColumnEmbedded\" \"CriteriaEmbedded\" " +
                "\"ColumnSelection\" não encontrada para o nome\"" +
                alias +"\"!");
    }

    /**
     *
     * @param field
     * @return
     * @throws AnnotationsUtilitiesException
     */
    public static GridColumn readGridColumnAnnotation(Field field)
            throws AnnotationsUtilitiesException{

        if (field == null)
            throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nParâmentro \"field\" não pode ser nulo!");

        if(!field.isAnnotationPresent(GridColumn.class))
            throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nAnnotation \"GridColumn\" não encontrada!");

        return field.getAnnotation(GridColumn.class);
    }

    /**
     * 
     * @param gc
     * @param alias
     * @return
     * @throws AnnotationsUtilitiesException
     */
    public static ColumnDefinition readColumnDefinition(GridColumn gc,
            String alias) throws AnnotationsUtilitiesException {
        
        if(gc == null)
            throw new AnnotationsUtilitiesException(MESSAGE +
                    "\nParâmentro \"GridColumn\" não pode ser nulo!");

        if(alias == null)
            throw new AnnotationsUtilitiesException(MESSAGE +
                    "\nParâmentro \"alias\" não pode ser nulo!");

        for (int i = 0; i < gc.definitions().length; i++) {
            ColumnDefinition cd = gc.definitions()[i];
            for (int j = 0; j < cd.alias().length; j++) {
                if (cd.alias()[j].equals(alias))
                    return cd;
            }
        }

        throw new AnnotationsUtilitiesException(MESSAGE +
                    "\nAnnotation \"ColumnDefinition\" não encontrada para o " +
                    "nome\""+ alias +"\"!");
    }

    /**
     *
     * @param gc
     * @param alias
     * @return
     * @throws AnnotationsUtilitiesException
     */
    public static ColumnEmbedded readEmbeddedColumns(GridColumn gc,
            String alias) throws AnnotationsUtilitiesException {

        if(gc == null)
            throw new AnnotationsUtilitiesException(MESSAGE +
                    "\nParâmentro \"GridColumn\" não pode ser nulo!");

        if(alias == null)
            throw new AnnotationsUtilitiesException(MESSAGE +
                    "\nParâmentro \"alias\" não pode ser nulo!");

        for (int i = 0; i < gc.embedded().length; i++) {
            ColumnEmbedded ce = gc.embedded()[i];
            for (int j = 0; j < ce.alias().length; j++) {
                if (ce.alias()[j].equals(alias))
                    return ce;
            }
        }

        throw new AnnotationsUtilitiesException(MESSAGE +
                    "\nAnnotation \"ColumnEmbedded\" não encontrada para o " +
                    "nome\""+ alias +"\"!");
    }

    /**
     *
     * @param gc
     * @param alias
     * @return
     * @throws AnnotationsUtilitiesException
     */
    public static ColumnSelection readSelectionColumns(GridColumn gc,
            String alias) throws AnnotationsUtilitiesException {

        if(gc == null)
            throw new AnnotationsUtilitiesException(MESSAGE +
                    "\nParâmentro \"GridColumn\" não pode ser nulo!");

        if(alias == null)
            throw new AnnotationsUtilitiesException(MESSAGE +
                    "\nParâmentro \"alias\" não pode ser nulo!");

        for (int i = 0; i < gc.selection().length; i++) {
            ColumnSelection cs = gc.selection()[i];
            for (int j = 0; j < cs.alias().length; j++) {
                if(cs.alias()[j].equals(alias))
                    return cs;
            }
        }

        throw new AnnotationsUtilitiesException(MESSAGE +
                    "\nAnnotation \"ColumnSelection\" não encontrada para o " +
                    "nome\""+ alias +"\"!");
    }

    /**
     *
     * @param list
     * @throws AnnotationsUtilitiesException
     */
    public static void sort(List<ColumnUtilities> list)
            throws AnnotationsUtilitiesException{

        if (list == null)
            throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nParâmentro \"list\" não pode ser nulo!");

        //Ordena a lista
        Collections.sort(list, new Comparator<ColumnUtilities>(){
            @Override
            public int compare(ColumnUtilities o1,
                    ColumnUtilities o2) {

                int order1 = o1.index;
                int order2 = o2.index;

                // compara
                return order1 < order2 ? -1 : (order1 > order2 ? 1 : 0);

            }
        });
    }

    /**
     *
     * @param field
     * @param alias
     * @return
     * @throws AnnotationsUtilitiesException
     */
    public static boolean isSelection(Field field, String alias)
            throws AnnotationsUtilitiesException{

        if (field == null)
            throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nParâmentro \"field\" não pode ser nulo!");

        if (alias == null)
            throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nParâmentro \"alias\" não pode ser nulo!");

        if (field.isAnnotationPresent(Selection.class)){
            Selection selection = field.getAnnotation(Selection.class);
            for (String s : selection.aliasForColuns()) {
                if(s.equals(alias))
                    return true;
            }
        }

        return false;
    }

    /**
     * Conta o número de colunas.
     * No caso do tipo ser "DEFINITION" o resultado será sempre um (1),
     * caso contrario dependera dos itens da lista "embedded".
     * @return O número de itens.
     */
    public int count(){
        if(type == DEFINITION)
            return 1;

        int count = 0;
        for (ColumnUtilities gcu : embedded) {
            count += gcu.count();
        }
        return count;
    }

    /**
     * Verifica se a classe possui um método get para o campo que a coluna
     * representa. O método get sempre deve começar com "get" e ser seguido pelo
     * nome do campo, sendo que a primeira letra do campo será passada para
     * maiúsculo.<br>
     * O método ainda deve retornar um objeto igual ao do campo.
     *
     * @param entityClass Classe que deve conter o method get.
     * @return o método get para o campo.
     *
     */
    public Method getGetMethod(Class entityClass)
            throws AnnotationsUtilitiesException{

        if (field != null){
            String fieldName = field.getName();
            String methodName = "get" + fieldName.substring(0, 1).toUpperCase()
                    + fieldName.substring(1);

            try{
                Method m = entityClass.getMethod(methodName);
                if (m.getReturnType().equals(field.getType()))
                    return m;
            }catch(NoSuchMethodException ex){
                throw new AnnotationsUtilitiesException("get Method for field "
                        + fieldName + " not found!",ex.getCause());
            }catch(SecurityException ex){
                throw new AnnotationsUtilitiesException("get Method for field "
                        + fieldName + " is not a public method!",ex.getCause());
            }
        }

        throw new AnnotationsUtilitiesException(MESSAGE +
                "\n get Method for field " + field.getName() + " not found!");
    }

    /**
     * Verifica se a classe possui um método set para o campo que a coluna
     * representa. O método set sempre deve começar com "set" e ser seguido pelo
     * nome do campo, sendo que a primeira letra do campo será passada para
     * maiúsculo.<br>
     * O método inda deve ter um parametro do mesmo tipo do campo.<br>
     *
     * @param entityClass
     * @return
     */
    public Method getSetMethos(Class entityClass)
            throws AnnotationsUtilitiesException{

        if (field != null){
            String fieldName = field.getName();
            String methodName = "set" + fieldName.substring(0, 1).toUpperCase()
                    + fieldName.substring(1);
            try{
                return entityClass.getMethod(methodName,field.getType());
            }catch(NoSuchMethodException ex){
                throw new AnnotationsUtilitiesException("set Method for field "
                        + fieldName + " not found!",ex.getCause());
            }catch(SecurityException ex){
                throw new AnnotationsUtilitiesException("set Method for field "
                        + fieldName + " is not a public method!",ex.getCause());
            }
        }

        throw new AnnotationsUtilitiesException(MESSAGE +
                "\n set Method for field " + field.getName() + " not found!");
    }
}
